﻿using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using NAudio.Wave;
using SharpAvi.Codecs;
using SharpAvi.Output;

namespace AVIFile
{
    public class VideoWriter : IDisposable
    {
        #region 成员变量
        //视频写入
        private AviWriter writer;
        //视频数据流
        private IAviVideoStream video_stream;
        //音频流
        private IAviAudioStream audio_stream;

        private SupportedWaveFormat audioWaveFormat;

        //音频源
        private WaveInEvent audioSource;
        private float ErrSum = 0;
        private int Fps;
        TimeSpan old_time = TimeSpan.Zero;

        private readonly AutoResetEvent stopThread = new AutoResetEvent(false);
        private readonly AutoResetEvent videoFrameWritten = new AutoResetEvent(false);
        private readonly AutoResetEvent audioBlockWritten = new AutoResetEvent(false);

        #endregion

        #region 属性

        public bool IsRunning { get; private set; }

        #endregion

        #region 单例

        private static VideoWriter instance;
        private static object lock_obj = new object();
        public static VideoWriter Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (lock_obj)
                    {
                        if (instance == null)
                        {
                            instance = new VideoWriter();
                        }
                    }
                }
                return instance;
            }
        }


        #endregion

        private VideoWriter()
        {
            SetDllPath();
        }

        private void SetDllPath()
        {
            // Set LAME DLL path for MP3 encoder
            var asmDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
#if FX45
            var is64BitProcess = Environment.Is64BitProcess;
#else
            var is64BitProcess = IntPtr.Size * 8 == 64;
#endif
            var dllName = string.Format("lameenc{0}.dll", is64BitProcess ? "64" : "32");
            Mp3AudioEncoderLame.SetLameDllLocation(Path.Combine(asmDir, dllName));
        }

        public void Open(string path, int width, int height, int fps = -1, bool record_audio = false)
        {
            GC.Collect();
            IsRunning = true;
            Console.WriteLine("Begin Write Video File");
            writer = new AviWriter(path);
            if (fps > 0) Fps = fps;
            writer.FramesPerSecond = Fps;
            writer.EmitIndex1 = true;
            video_stream = writer.AddMotionJpegVideoStream(width, height, 60);
            videoFrameWritten.Set();
            if (record_audio)
            {
                //用户选择了有效的音频设备
                audioWaveFormat = SupportedWaveFormat.WAVE_FORMAT_44M16;
                WaveFormat waveFormat = ToWaveFormat(audioWaveFormat);

                audioSource = new WaveInEvent
                {
                    DeviceNumber = 0,
                    WaveFormat = waveFormat,
                    // Buffer size to store duration of 1 frame
                    BufferMilliseconds = (int)Math.Ceiling(1000 / writer.FramesPerSecond),
                    NumberOfBuffers = 3,
                };

                if (audioSource != null)
                {
                    try
                    {
                        audioBlockWritten.Reset();
                        audioSource.DataAvailable += audioSource_DataAvailable;
                        audioSource.StartRecording();
                        audio_stream = writer.AddMp3AudioStream(waveFormat.Channels, waveFormat.SampleRate, 160);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        audioSource = null;
                        audio_stream = null;
                    }
                }
            }
        }

        /// <summary>
        /// 获取到二进制音频数据回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void audioSource_DataAvailable(object sender, WaveInEventArgs e)
        {
            var signalled = WaitHandle.WaitAny(new WaitHandle[] { videoFrameWritten, stopThread });
            if (signalled == 0)
            {
                audio_stream.WriteBlock(e.Buffer, 0, e.BytesRecorded);
                audioBlockWritten.Set();
            }
        }

        private static WaveFormat ToWaveFormat(SupportedWaveFormat waveFormat)
        {
            switch (waveFormat)
            {
                case SupportedWaveFormat.WAVE_FORMAT_44M16:
                    return new WaveFormat(44100, 16, 1);
                case SupportedWaveFormat.WAVE_FORMAT_44S16:
                    return new WaveFormat(44100, 16, 2);
                default:
                    throw new NotSupportedException("Wave formats other than '16-bit 44.1kHz' are not currently supported.");
            }
        }

        /// <summary>
        /// 写入图像帧到视频文件
        /// </summary>
        /// <param name="array">要写入位图的RGB数据</param>
        /// <param name="timeSpan">距离视频起始时刻的时间</param>
        public void WriteVideoFrame(byte[] array, TimeSpan timeSpan)
        {
            //计算帧间隔
            TimeSpan ts = timeSpan - old_time;
            if (ts > TimeSpan.Zero)
            {
                old_time = timeSpan;
                float err = (float)ts.TotalMilliseconds - 1000f / Fps;
                //累计误差
                ErrSum += err;
                //计算在当前累计时间误差下，需要插入多少帧
                int count = (int)(ErrSum * Fps / 1000f + 1);
                //保留没用完的时间误差
                ErrSum = ErrSum - (count - 1) * (1000f / Fps);
                int signalled = 0;
                if (audioSource != null)
                    signalled = WaitHandle.WaitAny(new WaitHandle[] { audioBlockWritten, stopThread });
                if (signalled == 0)
                {
                    Trace.TraceInformation("Count:" + count);
                    for (int i = 0; i < count; i++)
                    {
                        //调用SharpAVI异步写入接口
                        video_stream?.WriteFrameAsync(true, array, 0, array.Length);
                    }
                    if (audioSource != null)
                        videoFrameWritten.Set();
                }
            }
        }

        public void Close()
        {
            Console.WriteLine("Close() 0");

            stopThread.Set();
            old_time = TimeSpan.Zero;
            if (audioSource != null)
            {
                audioSource.StopRecording();
                audioSource.DataAvailable -= audioSource_DataAvailable;
            }
            Console.WriteLine("writer?.Close() 1");

            writer?.Close();
            Console.WriteLine("writer?.Close() 2");
            IsRunning = false;
            Console.WriteLine("End Write Video File");
        }

        public void Dispose()
        {
            Console.WriteLine("AVIWriter -> Dispose");
            this.Close();
        }
    }


}
